package mybatisplus.generator.engine;

import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.config.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;
import com.baomidou.mybatisplus.generator.config.po.TableField;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.io.Files;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.lang3.StringUtils;

import javax.validation.constraints.NotNull;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * 自定义引擎
 *
 * @author ${author}
 * @since 2022-06-28 11:43
 */
@Slf4j
public class CustomFreemarkerTemplateEngine extends FreemarkerTemplateEngine {

    /**
     * 模板文件夹
     */
    private static final String FOLDER_TEMPLATES = "/templates";

    /**
     * 模板后缀
     */
    private static final String TEMPLATE_SUFFIX = "ftl";

    /**
     * JAVA模板后缀
     */
    private static final String JAVA_TEMPLATE_SUFFIX = "java.ftl";

    /**
     * parentPackagePath
     */
    private static final String PARENT_PACKAGE_PATH = "parentPackagePath";

    /**
     * api
     */
    private static final String API = "api";

    /**
     * application
     */
    private static final String APPLICATION = "application";

    /**
     * application
     */
    private static final String COMMON = "common";

    /**
     * domain
     */
    private static final String DOMAIN = "domain";

    /**
     * infrastructure
     */
    private static final String INFRASTRUCTURE = "infrastructure";

    /**
     * Entity 前缀
     */
    private static final String PREFIX_ENTITY = "Entity";

    /**
     * IEntity 前缀
     */
    private static final String PREFIX_IENTITY = "IEntity";

    /**
     * JAVA 资源路径
     */
    private static final String JAVA_PATH = Paths.get("src", "main", "java").toString();


    @Override
    public Map<String, Object> getObjectMap(ConfigBuilder config, TableInfo tableInfo) {
        Map<String, Object> objectMap = super.getObjectMap(config, tableInfo);

        // 遍历字段
        for (TableField field : tableInfo.getFields()) {
            // 主键字段
            if (field.isKeyFlag()) {
                // 如果主键是自增类型，设置 idType 为 AUTO
                if (field.isKeyIdentityFlag()) {
                    objectMap.put("idType", "AUTO");
                } else if ("String".equals(field.getPropertyType())) {
                    // objectMap.put("idType", "ASSIGN_UUID");
                // } else {
                    objectMap.put("idType", "ASSIGN_ID");
                }
                objectMap.put("idPropertyType", field.getPropertyType());
            } else if (field.getColumnName().equalsIgnoreCase(config.getStrategyConfig().entity().getLogicDeleteColumnName())) {
                objectMap.put("logicDeletePropertyName", field.getPropertyName());
                objectMap.put("enableLogicDelete", true);
            }
        }


        PackageConfig packageConfig = config.getPackageConfig();
        if (StringUtils.isBlank(packageConfig.getModuleName())) {
            throw new IllegalArgumentException("请配置 moduleName");
        }
        if (Objects.equals(packageConfig.getModuleName(), packageConfig.getParent())) {
            throw new IllegalArgumentException("请配置 parent");
        }

        // 放置模块名称
        objectMap.put("moduleName", packageConfig.getModuleName());

        // 获取父包
        String parentPackage = packageConfig.getParent();
        String parentPackagePath;

        // 如果模块名称不为空，从父包中删除模块名称
        parentPackage = parentPackage.substring(0, parentPackage.length() - (packageConfig.getModuleName().length() + 1));

        objectMap.put("parentPackage", parentPackage);


        // 获取父包路径列表
        List<String> parentPackageList = Splitter.on(StringPool.DOT).omitEmptyStrings().splitToList(parentPackage);
        parentPackagePath = Joiner.on(File.separator).join(parentPackageList);
        objectMap.put(PARENT_PACKAGE_PATH, parentPackagePath);

        Map<String, String> packageInfo = new HashMap<>();
        packageInfo.put(APPLICATION, Joiner.on(StringPool.DOT).skipNulls().join(parentPackage, APPLICATION));
        packageInfo.put(COMMON, Joiner.on(StringPool.DOT).skipNulls().join(parentPackage, COMMON));
        packageInfo.put(DOMAIN, Joiner.on(StringPool.DOT).skipNulls().join(parentPackage, DOMAIN));
        packageInfo.put(INFRASTRUCTURE, Joiner.on(StringPool.DOT).skipNulls().join(parentPackage, INFRASTRUCTURE));

        objectMap.put("package", Collections.unmodifiableMap(packageInfo));

        return objectMap;
    }

    /**
     * 批量输出 java xml 文件
     */
    @Override
    public AbstractTemplateEngine batchOutput() {
        try {
            ConfigBuilder config = this.getConfigBuilder();
            // 获取表信息列表
            List<TableInfo> tableInfoList = config.getTableInfoList();
            // 获取模板地址
            URL templatesUrl = this.getClass().getResource(FOLDER_TEMPLATES);
            if (templatesUrl == null) {
                throw new IllegalArgumentException(FOLDER_TEMPLATES + " 不存在");
            } else {
                System.out.println("获取模板文件路径："+templatesUrl);
            }

            // 模板路径
            String templatePath = templatesUrl.getPath();

            // 获取输出目录
            String outPath = config.getGlobalConfig().getOutputDir();

            // 循环处理表信息
            for (TableInfo tableInfo : tableInfoList) {
                Map<String, Object> objectMap = this.getObjectMap(config, tableInfo);

                Optional.ofNullable(config.getInjectionConfig()).ifPresent(injection -> {
                    injection.beforeOutputFile(tableInfo, objectMap);
                    Boolean isFirstTime = (Boolean) injection.getCustomMap().get("isFirstTime");
                    // 只有第一次生成代码，才会生成这两个模块
                    if (isFirstTime) {
                        // 生成 api 模块
                        generatorApi(tableInfo, objectMap, templatePath, outPath);
                        // 生成 common 模块
                        generatorCommon(tableInfo, objectMap, templatePath, outPath);
                    }

                    // 生成 application 模块
                    generatorApplication(tableInfo, objectMap, templatePath, outPath);

                    // 生成 domain 模块
                    generatorDomain(tableInfo, objectMap, templatePath, outPath);

                    // 生成 infrastructure 模块
                    generatorInfrastructure(tableInfo, objectMap, templatePath, outPath);
                });
            }
            PackageConfig packageConfig = config.getPackageConfig();

            String parentPackage = packageConfig.getParent();
            parentPackage = parentPackage.substring(0, parentPackage.length() - (packageConfig.getModuleName().length() + 1));

            Map<String, Object> objectMap = Maps.newHashMap();
            objectMap.put("moduleName", packageConfig.getModuleName());
            objectMap.put("parentPackage", parentPackage);

            // 输出其他文件
            outputOther(objectMap, templatePath, outPath);
        } catch (Exception e) {
            log.info(e.getMessage());
            throw new IllegalArgumentException("无法创建文件，请检查配置信息！", e);
        }
        return this;
    }

    /**
     * 生成 api 模块
     *
     * @param tableInfo    表信息
     * @param objectMap    渲染数据
     * @param templatePath 模板路径
     * @param outPath      输出路径
     */
    protected void generatorApi(@NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap, String templatePath, String outPath) {
        // application 模板文件路径
        String appTemplatePath = Joiner.on(File.separator).join(templatePath, API);

        output(tableInfo, objectMap, appTemplatePath, outPath);
    }

    /**
     * 生成 application 模块
     *
     * @param tableInfo    表信息
     * @param objectMap    渲染数据
     * @param templatePath 模板路径
     * @param outPath      输出路径
     */
    protected void generatorApplication(@NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap, String templatePath, String outPath) {
        // application 模板文件路径
        String appTemplatePath = Joiner.on(File.separator).join(templatePath, APPLICATION);

        output(tableInfo, objectMap, appTemplatePath, outPath);
    }

    /**
     * 生成 common 模块
     *
     * @param tableInfo    表信息
     * @param objectMap    渲染数据
     * @param templatePath 模板路径
     * @param outPath      输出路径
     */
    protected void generatorCommon(@NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap,
                              String templatePath, String outPath) {
        // common 模板文件路径
        String appTemplatePath = Joiner.on(File.separator).join(templatePath, COMMON);

        output(tableInfo, objectMap, appTemplatePath, outPath);
    }

    /**
     * 生成 domain 模块
     *
     * @param tableInfo    表信息
     * @param objectMap    渲染数据
     * @param templatePath 模板路径
     * @param outPath      输出路径
     */
    protected void generatorDomain(@NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap, String templatePath, String outPath) {
        // domain 模板文件路径
        String domainTemplatePath = Joiner.on(File.separator).join(templatePath, DOMAIN);

        output(tableInfo, objectMap, domainTemplatePath, outPath);
    }

    /**
     * 生成 infrastructure 模块
     *
     * @param tableInfo    表信息
     * @param objectMap    渲染数据
     * @param templatePath 模板路径
     * @param outPath      输出路径
     */
    protected void generatorInfrastructure(@NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap, String templatePath, String outPath) {
        // infrastructure 模板文件路径
        String infrastructureTemplatePath = Joiner.on(File.separator).join(templatePath, INFRASTRUCTURE);

        output(tableInfo, objectMap, infrastructureTemplatePath, outPath);
    }

    /**
     * 输出App文件
     *
     * @param tableInfo 表信息
     * @param objectMap 渲染数据
     * @since 3.5.0
     */
    protected void output(@NotNull TableInfo tableInfo, @NotNull Map<String, Object> objectMap, String templatePath, String outPath) {
        // 获取指定模板目录下所有模板文件，不递归获取
        List<File> templateFiles = getAllJavaTemplateFile(templatePath);

        // 计算路径中 /templates 位置
        int templateRootIndex = templatePath.indexOf(FOLDER_TEMPLATES);
        // 计算根路径位置
        templateRootIndex += FOLDER_TEMPLATES.length();
        int javaPathIndex;
        File tmpDestFile;
        String filename;
        String outFilename;
        String parentRelativePath;
        for (File templateFile : templateFiles) {
            // 获取父目录相对路径
            parentRelativePath = templateFile.getParent().substring(templateRootIndex);
            javaPathIndex = parentRelativePath.indexOf(JAVA_PATH);
            if (javaPathIndex == -1) {
                throw new IllegalArgumentException("java 模板必须放到 src/main/java 下面，当前模板路径" + parentRelativePath);
            }
            // 获取文件全名，带扩展名
            filename = FilenameUtils.getName(templateFile.getPath());
            // 获取文件名（不含扩展名）
            outFilename = FilenameUtils.getBaseName(filename);
            //
            if (outFilename.startsWith(PREFIX_ENTITY)) {
                outFilename = tableInfo.getEntityName() + outFilename.substring(PREFIX_ENTITY.length());
            } else if (outFilename.startsWith(PREFIX_IENTITY)) {
                outFilename = "I" + tableInfo.getEntityName() + outFilename.substring(PREFIX_IENTITY.length());
            }
            tmpDestFile = new File(Joiner.on(File.separator).join(outPath, parentRelativePath.substring(0, javaPathIndex + JAVA_PATH.length()),
                    objectMap.get(PARENT_PACKAGE_PATH), parentRelativePath.substring(javaPathIndex + JAVA_PATH.length()), outFilename));
            outputFile(tmpDestFile, objectMap, Joiner.on(File.separator).join(FOLDER_TEMPLATES, parentRelativePath, filename), true);
        }
    }

    /**
     * 输出非模板文件
     *
     * @param objectMap    渲染数据
     * @param templatePath 模板路径
     * @param outPath      输出目录
     */
    protected void outputOther(@NotNull Map<String, Object> objectMap, String templatePath, String outPath) throws IOException {

        // 获取所有非JAVA模板文件
        List<File> templateFiles = getAllNotJavaTemplateFile(new File(templatePath));

        // 根路径位置
        // TODO windows和mac的路径不一样，所以mac不需要-1
        // int rootIndex = templatePath.length();
        int rootIndex = templatePath.length();

        File tmpDestFile;
        String filename;
        String extName;
        String outFilename;
        String parentRelativePath;
        for (File templateFile : templateFiles) {
            // 获取父目录相对路径
            parentRelativePath = templateFile.getParent().substring(rootIndex);
            // 获取文件全名，带扩展名
            filename = FilenameUtils.getName(templateFile.getPath());
            extName = FilenameUtils.getExtension(filename);
            // 获取文件名（不含扩展名）
            outFilename = FilenameUtils.getBaseName(filename);

            // 模板文件
            if (extName.equalsIgnoreCase(TEMPLATE_SUFFIX)) {
                tmpDestFile = new File(Joiner.on(File.separator).join(outPath, parentRelativePath, outFilename));
                outputFile(tmpDestFile, objectMap, Joiner.on(File.separator).join(FOLDER_TEMPLATES, parentRelativePath, filename), true);
            } else {
                tmpDestFile = new File(Joiner.on(File.separator).join(outPath, parentRelativePath, filename));
                File parentFile = tmpDestFile.getParentFile();
                if (!parentFile.exists()) {
                    boolean success = parentFile.mkdirs();
                }
                Files.copy(templateFile, tmpDestFile);
            }
        }
    }

    /**
     * 返回所有非JAVA模板文件
     *
     * @param folder 文件夹
     * @return 返回非JAVA模板文件（其他模板和非模板文件）
     */
    protected List<File> getAllNotJavaTemplateFile(@NotNull File folder) {
        IOFileFilter dirFilter = DirectoryFileFilter.DIRECTORY;
        IOFileFilter fileFilter = FileFilterUtils.asFileFilter(file -> {
            // 非文件
            if (!file.isFile()) {
                return false;
            }
            // 获取文件扩展名
            return !file.getName().endsWith(JAVA_TEMPLATE_SUFFIX);
        });

        Collection<File> files = FileUtils.listFiles(folder, fileFilter, dirFilter);
        return Lists.newArrayList(files);
    }


    /**
     * 获取指定文件夹下指定扩展名文件，不递归获取子文件夹下的文件
     *
     * @param path 路径
     * @return 所有JAVA模板文件
     */
    protected List<File> getAllJavaTemplateFile(@NotNull String path) {
        File folder = new File(path);
        if (!folder.isDirectory()) {
            throw new IllegalArgumentException(path + " 文件夹不存在");
        }
        Collection<File> files = FileUtils.listFiles(folder, new String[]{JAVA_TEMPLATE_SUFFIX}, true);
        return Lists.newArrayList(files);
    }
}
